//
//  File.swift
//  
//
//  Created by 钟志远 on 2021/4/17.
//

import Fluent
import Vapor
import FluentPostgresDriver

class FileController: RouteCollection {
    func boot(routes: RoutesBuilder) throws {
//        routes.post("register", use: register)
        let fileRouter = routes.grouped("file")
        fileRouter.on(.PUT, "upload", ":fileName", body: .stream, use: fileUpload)
        fileRouter.get(use: fileList)
    }
    
    func fileList(req: Request) throws -> EventLoopFuture<[FileModel]> {
        let dirUUIDString: String? = try? req.query.get(at: "dirUUID")
        var uuid: UUID?
        if let str = dirUUIDString {
            uuid = UUID.init(uuidString: str)
        }
        
        return req.auth.get(User.self)!
            .$files.query(on: req.db)
            .filter(\.$parent.$id ==  (uuid != nil ? .bind(uuid!): .null) )
            .with(\.$childFile) { child in
                child.with(\.$fileType)
            }
            .all()

    }
    
    func saveFile(data: ByteBuffer, req: Request, path: String) -> EventLoopFuture<Void> {

         return req.application.fileio.openFile(path: path,
                                               mode: .write,
                                               flags: .allowFileCreation(posixMode: 0x744),
                                               eventLoop: req.eventLoop)
            .flatMap { handle in
                req.application.fileio.write(fileHandle: handle,
                                             buffer: data,
                                             eventLoop: req.eventLoop)
                    .flatMapThrowing { _ in
                        try handle.close()
                        return
                    }
            }
    }
    
    func createFileModel(user: User, db: Database, fileModel: FileModel) -> EventLoopFuture<Void> {

        return user.$files.query(on: db)
            .filter(\.$id == .null)
            .first()
            .unwrap(or: Abort(.badRequest))
            .flatMap { (dir: FileModel) -> EventLoopFuture<Void> in
                return dir.$childFile.create(fileModel, on: db)
            }

    }
    
    func fileUpload(req: Request) -> EventLoopFuture<String> {
        let fileName = req.parameters.get("fileName")!
        let fileExt = fileName.split(separator: ".").last
        
        let responsePromise = req.eventLoop.makePromise(of: String.self)
        
        var fileRealName = UUID().uuidString

        if let ext = fileExt {
            fileRealName += ("." + ext)
        }
        let path = req.application.directory.publicDirectory + "upload/" + fileRealName
        
        let fileHanderFuture = req.application.fileio.openFile(path: path,
                        mode: .write,
                        flags: .allowFileCreation(posixMode: 0x744),
                        eventLoop: req.eventLoop)
        
        var task: EventLoopFuture<NIOFileHandle>?
        
        var fileLength = 0
        req.body.drain { (bodyResult:BodyStreamResult) -> EventLoopFuture<Void> in
            switch bodyResult {
            case .buffer(let data):
                fileLength += data.readableBytes
                let fileWriteBlock = { (handler: NIOFileHandle) -> EventLoopFuture<NIOFileHandle> in
                    return req.application
                        .fileio
                        .write(fileHandle: handler,
                               buffer: data,
                               eventLoop: req.eventLoop)
                        .map{handler}
                }
                
                task = task != nil ? task!.flatMap(fileWriteBlock): fileHanderFuture.flatMap(fileWriteBlock)

            case .end:
                task?.flatMapThrowing { handler in
                        try handler.close()
                        return
                    }
                    .whenComplete({ (result) in
                        switch result {
                        case .success:
                            let user = req.auth.get(User.self)!
                            let model = FileModel(userId: user.id!, fileNameReal: fileRealName, fileNameShow: fileName, fileLength: fileLength)
                            let result = self.createFileModel(user: user, db: req.db, fileModel: model)
                            
                            result
                                .whenSuccess { () in
                                    responsePromise.succeed(path)
                                }
                            result.whenFailure { (error) in
                                responsePromise.fail(error)
                            }
                        case .failure(let err):
                            responsePromise.fail(err)
                        }
                    })
            case .error(let err):
                responsePromise.fail(err)
                return req.eventLoop.makeFailedFuture(err)
            }
            return req.eventLoop.makeSucceededVoidFuture()
        }
        
        
        responsePromise.futureResult
            .whenFailure { (err) in
                let url = URL.init(fileURLWithPath: path)
                let fm = FileManager.default
                do {
                    try fm.removeItem(at: url)
                } catch {
                    req.logger.report(error: error)
                }
            }
        
        
        return responsePromise.futureResult
    }
}

//extension EventLoopFuture {
//
//    func seriaCombain() {
//
//    }
//}
